<?php
/**
 * Author: FoxxxZhu
 * Date: 2016/9/23
 * Time: 18:46
 */

namespace Lavender\Db;


class Expression
{

    /**
     * @var string the DB expression
     */
    public $expression;
    /**
     * @var array list of parameters that should be bound for this expression.
     * The keys are placeholders appearing in [[expression]] and the values
     * are the corresponding parameter values.
     */
    public $params = [];

    /**
     * Constructor.
     * @param string $expression the DB expression
     * @param array $params parameters
     */
    public function __construct($expression, $params = [])
    {
        $this->expression = $expression;
        $this->params = $params;
    }

    /**
     * String magic method
     * @return string the DB expression
     */
    public function getSql(Mysql $db)
    {
        if(count($this->params) < 1)
        {
            return $this->expression;
        }
        $fromList = array_keys($this->params);
        $toList = array_values($this->params);
        foreach($toList as &$to)
        {
            if(is_numeric($to))
            {
                continue;
            }
            if(is_string($to))
            {
                $to = $db->escape($to);
            }
            if(is_array($to))
            {
                $to = $db->escape_array($to);
                $to = implode("','",$to);
            }
        }
        return str_replace($fromList, $toList, $this->expression);
    }

    /**
     * @param array|string $fields eg:"field" or ['field'=>value,'field2'=>val2]
     * @param string $value
     * @param string $left
     * @param string $right
     * @param string $link
     * @return static
     * @throws Exception
     */
    public static function MakeLike($fields, $value = '',$left = '%',$right = '%',$link = "AND")
    {
        $items = [];
        if (is_string($fields)) {
            $items[$fields] = $value;
        } elseif (is_array($fields)) {
            $items = $fields;
        } else {
            throw new Exception("wrong args!");
        }
        if(count($items)<1)
        {
            throw new Exception("fields can not be empty !");
        }
        $link = strtoupper($link);
        $linkList = ['AND','OR'];
        if(!in_array($link,$linkList))
        {
            throw new Exception("LINK {$link} can not be support !");
        }
        $expressions = [];
        $paramsKeys = [];
        $paramsValues = [];
        foreach($items as $field => $val)
        {
            if(!Mysql::check_field($field))
            {
                throw new Exception("field `{$field}` is wrong !");
            }
            $replace= "::search_{$field}::";
            $like = str_replace(["%","_"],['\%','\_'],$val);
            $paramsKeys[] = $replace;
            $paramsValues[] = $like;
            $expressions[] = "`{$field}` LIKE '{$left}{$replace}{$right}'";
        }
        return new static(implode(" {$link} ",$expressions),array_combine($paramsKeys,$paramsValues));
    }

    /**
     * @param array|string $fields eg:"field" or [ ['field','>',$num],['field','<',$num]]
     * @param string $compare
     * @param int $value
     * @param string $link
     * @return static
     * @throws Exception
     */
    public static function MakeCompare($fields, $compare = '>', $value = 0 ,$link = "AND")
    {
        $items = [];
        if (is_string($fields)) {
            $items[] = [$fields,$compare,$value];
        } elseif (is_array($fields)) {
            $items = $fields;
        } else {
            throw new Exception("wrong args!");
        }
        $link = strtoupper($link);
        $linkList = ['AND','OR'];
        if(!in_array($link,$linkList))
        {
            throw new Exception("LINK {$link} can not be support !");
        }
        $operaList = [">","<",">=","<=","<>","!=","="];
        $expressions = [];
        $paramsKeys = [];
        $paramsValues = [];
        foreach($items as $k => $compares)
        {

            if(!is_array($compares) || !isset($compares[0]) || !isset($compares[1]) || !isset($compares[2]))
            {
                throw new Exception("wrong compare expression!");
            }
            $field = $compares[0];
            $opera = $compares[1];
            $val = $compares[2];
            $operaVal = "::compare_val_{$k}::";
            if(!Mysql::check_field($field))
            {
                throw new Exception("field `{$field}` is wrong !");
            }
            if(!in_array($opera,$operaList))
            {
                throw new Exception("opera `{$opera}` can not support !");
            }
            $paramsKeys[] = $operaVal;
            $paramsValues[] = $val;
            $expressions[] = "`{$field}` {$opera} '{$operaVal}'";
        }
        return new static(implode(' AND ',$expressions),array_combine($paramsKeys,$paramsValues));
    }

    /**
     * @param array|string $fields eg:"field" or [ 'field1'=>[1,2,3],'field2'=>[1,2,3],..]
     * @param array $lists
     * @param string $link
     * @return static
     * @throws Exception
     */
    public static function MakeIn($fields, $lists = [],$link = "AND")
    {
        $items = [];
        if (is_string($fields)) {
            $items[$fields] = $lists;
        } elseif (is_array($fields)) {
            $items = $fields;
        } else {
            throw new Exception("wrong args!");
        }
        $link = strtoupper($link);
        $linkList = ['AND','OR'];
        if(!in_array($link,$linkList))
        {
            throw new Exception("LINK {$link} can not be support !");
        }
        $expressions = [];
        $paramsKeys = [];
        $paramsValues = [];
        foreach($items as $field => $list)
        {
            if(!is_array($list) || count($list)<1)
            {
                throw new Exception("wrong compare expression!");
            }
            if(!Mysql::check_field($field))
            {
                throw new Exception("field `{$field}` is wrong !");
            }
            $operaVal = "::in_{$field}::";
            $paramsKeys[] = $operaVal;
            $paramsValues[] = $list;
            $expressions[] = "`{$field}` IN ('{$operaVal}')";
        }
        return new static(implode(' AND ',$expressions),array_combine($paramsKeys,$paramsValues));
    }

    /**
     * @param string | Lavender\Db\Expression $rawSql
     * @param array $params
     * @return static
     * @throws Exception
     */
    public static function MakeRaw($rawSql, $params = [])
    {
        if(is_string($rawSql))
        {
            return new static($rawSql,$params);
        }
        elseif($rawSql instanceof Expression)
        {
            $rawSql->params = $params;
            return $rawSql;
        }
        else
        {
            throw new Exception("unknown rawSql");
        }
    }
}